/*
 * Famon (http://code.google.com/p/famon/)
 * Copyright (c) 2010 Carl Crous.
 *
 * This file is part of Famon.
 *
 * Famon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Famon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Famon.  If not, see <http://www.gnu.org/licenses/>.
 */

package za.org.foobar.famon.frontend.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import org.gwtportlets.portlet.client.AbstractWidgetFactory;
import org.gwtportlets.portlet.client.WidgetFactory;
import org.gwtportlets.portlet.client.WidgetRefreshHook;
import org.gwtportlets.portlet.client.event.BroadcastManager;
import org.gwtportlets.portlet.client.event.PageChangeEvent;
import org.gwtportlets.portlet.client.layout.Container;
import org.gwtportlets.portlet.client.layout.LayoutUtil;
import org.gwtportlets.portlet.client.ui.ClientAreaPanel;
import org.gwtportlets.portlet.client.ui.LayoutPanel;
import org.gwtportlets.portlet.client.ui.LoadingWidget;
import org.gwtportlets.portlet.client.util.SyncToClientArea;

/**
 * EntryPoint (main) class for the famon application. Fetches the root page
 * layout and uses this to create the GUI. Loads additional pages when the
 * history token changes.
 */
public class Famon implements EntryPoint {

    // The ClientAreaPanel always fills the whole of the browsers client area.
    // It forms the topmost container for the application and adds itself to
    // the normal GWT RootPanel in its constructor. It uses a LayoutPanel
    // internally
    private ClientAreaPanel clientAreaPanel = new ClientAreaPanel();
    // The pageEditor is responsible for editing and saving pages
    private FamonPageEditor pageEditor = new FamonPageEditor();
    // The instance of the app
    private static Famon instance;
    private int showBusyCount;
    private LoadingWidget loadingWidget = new LoadingWidget();
    private SyncToClientArea syncLoadingWidget = new SyncToClientArea(loadingWidget);
    private FamonPage page;

    public Famon() {
        instance = this;
    }

    public static Famon getInstance() {
        return instance;
    }

    public boolean isBusy() {
        return showBusyCount > 0;
    }

    public void showBusy() {
        if (++showBusyCount == 1) {
            clientAreaPanel.setLoading(true);
//            RootPanel.get().add(loadingWidget);
//            syncLoadingWidget.resizeWidget();
            //hideAsyncBusy(true);
        }
    }

    public void hideBusy() {
        if (--showBusyCount <= 0) {
//            RootPanel.get().remove(loadingWidget);
            clientAreaPanel.setLoading(false);
            showBusyCount = 0;
        }
    }

    public int getShowBusyCount() {
        return showBusyCount;
    }

    public void onModuleLoad() {
        GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
            public void onUncaughtException(Throwable e) {
                handleException(e);
            }
        });
        // The framework calls the WidgetRefreshHandler instance when it
        // needs to refresh a Portlet or Widget with new data from the server.
        // This handler just calls our refresh service method.
        WidgetRefreshHook.App.set(new WidgetRefreshHook() {
            public void refresh(Widget w, WidgetFactory wf,
                    AsyncCallback<WidgetFactory> cb) {
                FamonService.App.get().refresh(History.getToken(), wf, cb);
            }
            public void onRefreshCallFailure(Widget w, Throwable caught) {
                onPageChange(null);
                handleException(caught);
            }
        });
        History.addValueChangeHandler(new ValueChangeHandler<String>() {
            public void onValueChange(ValueChangeEvent<String> event) {
                onHistoryChanged(event.getValue());
            }
        });
        fetchRootPage();
    }

    /**
     * Create the main application GUI by fetching the 'root page' from the
     * server. The root page contains a {@link org.gwtportlets.portlet.client.ui.PagePortlet}
     * which forms a 'content area' to display the selected page.
     */
    private void fetchRootPage() {
        FamonService.App.get().getRootPage(History.getToken(),
                new AsyncCallback<FamonPage>() {
            public void onFailure(Throwable caught) {
                handleException(caught);
            }
            public void onSuccess(FamonPage p) {
                // Hide the initial loading spinner from Famon.html
                Element e = DOM.getElementById("loading");
                if (e != null) {
                    DOM.setStyleAttribute(e, "display", "none");
                }
                Famon.this.page = p;
                clientAreaPanel.add(LayoutUtil.createWidget(p.rootWidgetFactory));
                clientAreaPanel.layout();
                onPageChange(p);
            }
        });
    }

    /**
     * Notify all portlets that the page has changed. The
     * {@link org.gwtportlets.portlet.client.ui.PagePortlet} uses this
     * notification to display the new page.
     */
    private void onPageChange(final FamonPage p) {
        final boolean notFound = p != null && p.widgetFactory == null;
        if (notFound) {
            // Put in a 'not found' message
            p.widgetFactory = new AbstractWidgetFactory() {
                public Widget createWidget() {
                    LayoutPanel lp = new LayoutPanel();
                    lp.add(new Label("Page not found: '" + p.pageName +
                            "', click the edit page button to create it"));
                    return lp;
                }
            };
        }
        page = p;
        if (page == null) {
            page = new FamonPage();
        }

        // The page change event knows how to edit the current page
        PageChangeEvent pce = new PageChangeEvent(this) {
            public void editPage(Container container) {
                if (notFound) {
                    // get rid of the 'not found' message
                    container.clear();
                }
                pageEditor.startEditing(getPageName(), container);
            }
        };
        pce.setPageName(page.pageName);
        pce.setEditable(page.canEditPage);
        pce.setWidgetFactory(page.widgetFactory);

        // Send the event to every AppEventListener in the container tree.
        // The PagePortlet uses this event to change the widget tree in the
        // 'content area' of the application and to display the gear icon
        // for editable pages
        BroadcastManager.get().broadcast(pce);
    }

    public FamonPage getPage() {
        return page;
    }

    /**
     * Load a new page of portlets and widgets when the history token changes.
     */
    public void onHistoryChanged(String historyToken) {
        // display AJAX pizza over whole app disabling input while the page loads
        showBusy();
        FamonService.App.get().getPage(historyToken, new AsyncCallback<FamonPage>() {
            public void onFailure(Throwable caught) {
                hideBusy();
                handleException(caught);
            }
            public void onSuccess(FamonPage p) {
                hideBusy();
                onPageChange(p);
            }
        });
    }

    public void refresh() {
        onHistoryChanged(History.getToken());
    }

    public void handleException(Throwable t) {
        hideBusy();
        String msg;
        if (t instanceof FamonException) {
            msg = t.getMessage();
        } else {
            msg = "Server Error: " + t;
        }
        Window.alert(msg);
//        StringBuilder b = new StringBuilder();
//        for (Object e : t.getStackTrace()) {
//            b.append(e).append("\n");
//        }
//        Window.alert(msg + "\n" + b.toString());
    }

}
